#include "HerzeleidPrecompiled.hxx"
#include "HerzeleidCoreApplication.hxx"
#include "HerzeleidCoreDispatcher.hxx"

#include "HerzeleidWindowSizeChangedEventArgs.hxx"
#include "HerzeleidCharacterReceivedEventArgs.hxx"
#include "HerzeleidVisibilityChangedEventArgs.hxx"
#include "HerzeleidWindowActivatedEventArgs.hxx"
#include "HerzeleidCoreWindowEventArgs.hxx"
#include "HerzeleidPointerEventArgs.hxx"
#include "HerzeleidKeyEventArgs.hxx"
#include "HerzeleidTypedEvent.hxx"
#include "HerzeleidCoreWindow.hxx"
#include "HerzeleidPoint.hxx"

using namespace HerzeleidUICoreNamespaceImpl;
using namespace HerzeleidUIInputNamespaceImpl;
using namespace HerzeleidFoundationNamespaceImpl;

#define HERZELEID_USE_MOUSE_DATA (1)
#define HERZELEID_USE_POINTER_DATA (0)

#define TaskCancellationRequested concurrency::is_task_cancellation_requested
#define CancelCurrentTask concurrency::cancel_current_task
#define MoveByRef std::move

// This is where the captured messages will be handled 
void CoreWindowEventHandler::ProcessEvents( _In_ CoreProcessEventsOptionConstRef eOption )
{
	switch ( eOption )
	{
	case CoreProcessEventsOption::ProcessOneAndAllIfPresent: 
		if ( m_bIgnoreProcessRequests ) return;
		else m_MsgDispatching = m_MsgDispatching.then( [this] ( Bool bCancelled )
		{
			if ( !bCancelled )
			{
				WindowMessage Msg;
				if ( TaskCancellationRequested() )
				{
					CancelCurrentTask();
					return( bCancelled );
				}
				else
				{
					if ( !m_Messages.empty() )
					{
						while ( !m_Messages.empty() )
						{
							if ( TaskCancellationRequested() )
							{
								CancelCurrentTask();
								return( bCancelled );
							}
							else if ( m_Messages.try_pop(Msg) ) 
								Process(Msg);
						}

						return( bCancelled );
					}
					else
					{
						while ( true )
						{
							if ( !m_Messages.empty() )
							{
								if ( TaskCancellationRequested() )
								{
									CancelCurrentTask();
									return( true );
								}
								else
								{
									if ( m_Messages.try_pop(Msg) ) 
									{
										Process(Msg);
										return( bCancelled );
									}
								}
							}
						}
					}
				}
			}

			return( bCancelled );
		}); break;
	case CoreProcessEventsOption::ProcessAllIfPresent:
		if ( m_bIgnoreProcessRequests ) return;
		else m_MsgDispatching = m_MsgDispatching.then( [this]( Bool bCancelled ) 
		{
			if ( bCancelled ) return( bCancelled );
			WindowMessage Msg;
			if ( TaskCancellationRequested() )
			{
				CancelCurrentTask();
				return( true );
			}
			else
			{
				if ( !m_Messages.empty() )
				{
					while ( !m_Messages.empty() )
					{
						if ( TaskCancellationRequested() )
						{
							CancelCurrentTask();
							return( true );
						}
						else if ( m_Messages.try_pop(Msg) ) 
							Process(Msg);
					}
				}
			}

			return (bCancelled);
		}); break;
	case CoreProcessEventsOption::ProcessOneIfPresent: 
		if ( m_bIgnoreProcessRequests ) return;
		else m_MsgDispatching = m_MsgDispatching.then( [this]( Bool bCancelled ) 
		{
			if ( bCancelled ) return( bCancelled );
			WindowMessage Msg;
			if ( TaskCancellationRequested() )
			{
				CancelCurrentTask();
				return( true );
			}
			else if ( !m_Messages.empty() )
				if (m_Messages.try_pop( Msg )) 
					Process(Msg);

			return (bCancelled);
		}); break;
	case CoreProcessEventsOption::ProcessUntilQuit: 
		m_bIgnoreProcessRequests = true;
		m_MsgDispatching = m_MsgDispatching.then( [this] (Bool bCancelled)
		{
			WindowMessage Msg;
			while (true)
			{
				if ( TaskCancellationRequested() )
				{
					CancelCurrentTask();
					return( true );
				}
				else
					if (m_Messages.try_pop( Msg )) 
						Process(Msg);
			}

			return( bCancelled );
		}); 
		
		AutoDebugVariable( Status ) m_MsgDispatching.wait();
		AutoDebugVariable( Result ) m_MsgDispatching.get();
		// TODO: Handle possible exceptions here
		break;
	}
}

// TODO: Ensure the window message queue size is consistent and do not exceed the max size
// TODO: Implement mouse enter/leave events (there are no default messages)
LResultConst CoreWindowEventHandler::WindowProc(
	_In_ WindowHandleConst hWnd,		// Window handle
	_In_ UInt32Const uiMsg,				// Window message
	_In_ WParamConst wParam,			// First parameter (historically word parameter)
	_In_ LParamConst lParam				// Second parameter (historically long parameter)
	) 
{
	//PrintToDebugWindowOnly8( "CoreWindowEventHandler > WindowProc : Got message" );

	if (!m_HostReferencePtr->IsInputEnabled) 
	{
		// The user does not want ro receive events 
		// All the event would be handled immediately. 
		return(DefWindowProc(
			hWnd, uiMsg, wParam, lParam
			));
	}
	else switch (uiMsg)
	{
	// Ignored events 
	//case WM_SYSKEYUP: // Alt + Key or F10 (menu bar) - That not worth handling for games
	//case WM_SYSKEYDOWN: // Alt + Key or F10 (menu bar) - That not worth handling for games 
		// return( 0 );

	// Here are the messages we want to respond to, but with possible delay.  
	case WM_ACTIVATE:  case WM_SIZE: // window state changes
	//case WM_SIZING: 
	//case WM_EXITSIZEMOVE:
	case WM_CHAR: case WM_KEYDOWN:  case WM_KEYUP: // keyboard state changes
	// Windows 7 does not support WM_POINTER events
	// Instead we can handle trivial mouse events 
	// TODO: Touch/gesture events 
#if HERZELEID_USE_MOUSE_DATA // Mouse event provide less useful data but enough for mouse users 
	case WM_MOUSEMOVE: // Mouse move 
	//case WM_TOUCH: // When the window is registered as a touch window 
	//case WM_GESTURE: // When the window is not registered as a touch window  
	//case WM_MOUSELEAVE: // Is posted with TrackMouseEvent with the TME_LEAVE flag included (sucks).
	case WM_LBUTTONDOWN:	case WM_LBUTTONUP: // left button
	case WM_RBUTTONDOWN:	case WM_RBUTTONUP: // right button
	case WM_MBUTTONDOWN:	case WM_MBUTTONUP: // middle button
	case WM_MOUSEHWHEEL:	case WM_MOUSEWHEEL: // wheel
#else
	// Windows 8 Desktop apps can handle WM_POINTER events
	// which are very handy and of great importance for us. 
	case WM_POINTERUPDATE: 
	case WM_POINTERHWHEEL:
	case WM_POINTERWHEEL:
	case WM_POINTERLEAVE:
	case WM_POINTERENTER:
	case WM_POINTERDOWN:
	case WM_POINTERUP:
#endif
		m_MsgCapturing = m_MsgCapturing.then( [this, hWnd, uiMsg, wParam, lParam] ( Bool bCancelled )
		{
			if ( TaskCancellationRequested() )
			{
				PrintToDebugWindowOnly8( "CoreWindowEventHandler > WindowProc : Cancelled" );
				CancelCurrentTask();
				return( true );
			}
			else
			{
				WindowMessage Msg(
					hWnd,		// Window handle
					uiMsg,		// Window message
					wParam,		// Fist param
					lParam		// Second param
					);

				// Enqueue event to process it later. 
				m_Messages.push(Msg);

				// TODO: The later events are more important rather then old `uns. 
				// The queue size should not exceed the max range. 
			}

			return( bCancelled );
		});

		// Message will be processed later. 
		return( 0 );

	// These events are urgent and should be handled immediately. 

	// The WM_QUIT will be handled in DefWindowProc and in the main message loop. 
	case WM_DESTROY:
		{
			m_bIgnoreProcessRequests = true;
			m_HostReferencePtr->m_bIsInputEnabled = false;
			m_HostReferencePtr->m_bIsVisible = false;

			// PrintToDebugWindowOnly8( "CoreWindowEventHandler > WindowProc : Destroy" );
			WindowActivatedEventArgsPtr pArgs = new WindowActivatedEventArgs(
				(CoreWindowActivationState)(0)
				);
			m_HostReferencePtr->Activated.Invoke( 
				m_HostReferencePtr, // Sender window 
				pArgs // Sender args
				);

			// TODO: Any actions to do before closing the dispatcher instance. 
			PostQuitMessage(0); // Post quit message to message queue
			DiscardAll(); // Discard all pending messages (there is no sense for further processing)
		} return( 0 ); // Return zero (the event is processed).

	// case WM_POWERBROADCAST: // !!!
		// TODO: This event is worth handling
		// TODO: Get AC status, force reducing display brightness, try reduce battery usage etc. 
		// Renderer should subscribe for this event (i.e. force using vsync etc)
		// or force using fixed timer step 

		// Closing window. 
	// We do not capture WM_CLOSE event. 
	case WM_CLOSE: // Let the user know the window is closing...
		//m_bIgnoreProcessRequests = true;
		m_HostReferencePtr->m_bIsInputEnabled = true;
		m_HostReferencePtr->m_bIsVisible = false;
		m_HostReferencePtr->Closed.Invoke(
			m_HostReferencePtr, new CoreWindowEventArgs()
			);
	// All the other messages should be handled there. 
	default: return(DefWindowProc(
		hWnd, uiMsg, wParam, lParam
		));
	}
}

// Some shortcuts for WM_KEYUP, WM_KEYDOWN, WM_CHAR events
#define HerzeleidExtractIsExtendedKey( lParam )	( lParam & 0x00100000 )	// bits=[24-24] + reserved=[25-28]
#define HerzeleidExtractIsKeyReleased( lParam )	( lParam & 0x04000000 )	// bits=[30-30]
#define HerzeleidExtractIsMenuKeyDown( lParam )	( lParam & 0x08000000 )	// bits=[31-31]
#define HerzeleidExtractRepeatCount( lParam )	( lParam & 0x0000ffff )	// bits=[00-15]
#define HerzeleidExtractWasKeyDown( lParam )	( lParam & 0x02000000 )	// bits=[29-29]
#define HerzeleidExtractScanCode( lParam )		( lParam & 0x00ff0000 )	// bits=[16-23]
using namespace HerzeleidSystemNamespaceImpl;

// This is where the captured messages will be handled 
// TODO: When reference to CoreWindow is added, modify CurrentPoint coords to client area coords 
// TODO: Touch/Gesture events, pointer identifiers etc
void CoreWindowEventHandler::Process( 
	_In_ CoreWindowEventHandler::WindowMessageConstRef Msg // Captured window message from the message queue 
	)
{
	switch (Msg.Message)
	{

#if HERZELEID_USE_MOUSE_DATA 

		// Pointer pressed events 
		
	case WM_LBUTTONDOWN: // Left button pressed
		{
			PointerEventArgsPtr pArgs = new PointerEventArgs();
			pArgs->RetrieveMouseData(Msg, PointerUpdateKind::LeftButtonPressed);
			
			// Raise event
			m_HostReferencePtr->PointerPressed.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;
	case WM_MBUTTONDOWN: // Middle button pressed
		{
			PointerEventArgsPtr pArgs = new PointerEventArgs();
			pArgs->RetrieveMouseData(Msg, PointerUpdateKind::MiddleButtonPressed);
			
			// Raise event
			m_HostReferencePtr->PointerPressed.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;
	case WM_RBUTTONDOWN: // Right button pressed
		{
			PointerEventArgsPtr pArgs = new PointerEventArgs();
			pArgs->RetrieveMouseData(Msg, PointerUpdateKind::RightButtonPressed);
			
			// Raise event
			m_HostReferencePtr->PointerPressed.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;

		// Pointer released events 

	case WM_LBUTTONUP: // Left button released
		{
			PointerEventArgsPtr pArgs = new PointerEventArgs();
			pArgs->RetrieveMouseData(Msg, PointerUpdateKind::LeftButtonReleased);
			
			// Raise event
			m_HostReferencePtr->PointerReleased.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;
	case WM_MBUTTONUP: // Middle button released
		{
			PointerEventArgsPtr pArgs = new PointerEventArgs();
			pArgs->RetrieveMouseData(Msg, PointerUpdateKind::MiddleButtonReleased);
			
			// Raise event
			m_HostReferencePtr->PointerReleased.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;
	case WM_RBUTTONUP: // Right button released
		{
			PointerEventArgsPtr pArgs = new PointerEventArgs();
			pArgs->RetrieveMouseData(Msg, PointerUpdateKind::RightButtonReleased);
			
			// Raise event
			m_HostReferencePtr->PointerReleased.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;

		// Wheel events
	case WM_MOUSEHWHEEL:
		//PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Tilt wheel" );
	case WM_MOUSEWHEEL:  
		{
			PointerEventArgsPtr pArgs = new PointerEventArgs();
			pArgs->RetrieveMouseData(Msg, PointerUpdateKind::Other);
			m_HostReferencePtr->PointerWheelChanged.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;

		// Pointer update events 
	case WM_MOUSEMOVE:
		{
			//PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Move" );
			PointerEventArgsPtr pArgs = new PointerEventArgs();
			pArgs->RetrieveMouseData(Msg, PointerUpdateKind::Other);
			m_HostReferencePtr->PointerMoved.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;

		// Pointer update events 
	//case WM_MOUSELEAVE:
	//	{
	//		m_bPointerExitedClientRect = true; // Indicate the mouse exited the client area
	//		PointerEventArgsPtr Args = new PointerEventArgs( );
	//		Args->RetrieveMouseData( Msg, PointerUpdateKind::Other );
	//		m_HostReferencePtr->PointerExited.Invoke( 
	//			m_HostReferencePtr, 
	//			Args 
	//			);

	//	} break;
	
	//case WM_TOUCH:
	//case WM_GESTURE:
	//	 break;

#else
	case WM_POINTERHWHEEL:
	case WM_POINTERWHEEL: // Mouse wheel
		{
			//PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Wheel" );
			PointerEventArgsPtr Args = new PointerEventArgs( Msg );

			// Raise event
			m_HostReferencePtr->PointerWheelChanged.Invoke( 
				m_HostReferencePtr, 
				Args 
				);
		} break;

	case WM_POINTERUPDATE: // Mouse move event 
		{
			//PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Update" );

			PointerEventArgsPtr Args = new PointerEventArgs( Msg );
			if (m_bPointerExitedClientRect)
			{
				m_bPointerExitedClientRect = false;
				m_HostReferencePtr->PointerEntered.Invoke( 
					m_HostReferencePtr, 
					Args 
					);
			}
			else 
			{
				m_HostReferencePtr->PointerMoved.Invoke( 
					m_HostReferencePtr, 
					Args 
					);
			}

		} break;

	case WM_POINTERLEAVE:
		{
			m_bPointerExitedClientRect = true; // We want to know when the pointer enters the client area

			//PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Leave" );
			PointerEventArgsPtr Args = new PointerEventArgs( Msg );
			
			// Raise event
			m_HostReferencePtr->PointerExited.Invoke( 
				m_HostReferencePtr, 
				Args 
				);
		} break;

	case WM_POINTERDOWN:
		{
			//PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Down" );
			PointerEventArgsPtr Args = new PointerEventArgs( Msg );
			
			// Raise event
			m_HostReferencePtr->PointerPressed.Invoke( 
				m_HostReferencePtr, 
				Args 
				);
		} break;

	case WM_POINTERUP:
		{
			//PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Up" );
			PointerEventArgsPtr Args = new PointerEventArgs( Msg );
			
			// Raise event
			m_HostReferencePtr->PointerReleased.Invoke( 
				m_HostReferencePtr, 
				Args 
				);
		} break;

	//case WM_POINTERENTER: // This event wont fire for the mouse input and should be handled manually. 
	//	{
	//		PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Enter" );
	//		PointerEventArgsPtr Args = new PointerEventArgs( Msg );
	//		
	//		// Raise event
	//		m_HostReferencePtr->PointerEntered.Invoke( 
	//			m_HostReferencePtr, 
	//			Args 
	//			);
	//	} break;

#endif

	case WM_CHAR: 
		{
			// PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Char" );
			CharacterReceivedEventArgsPtr pArgs = new CharacterReceivedEventArgs();

			// Fill out the structure
			pArgs->m_KeyCode = static_cast<UInt32>(Msg.WParam);
			pArgs->m_Status.RepeatCount = HerzeleidExtractRepeatCount(Msg.LParam);
			pArgs->m_Status.ScanCode = HerzeleidExtractScanCode(Msg.LParam);
			pArgs->m_Status.IsExtendedKey = HerzeleidExtractIsExtendedKey(Msg.LParam) ? true : false;
			pArgs->m_Status.IsKeyReleased = HerzeleidExtractIsKeyReleased(Msg.LParam) ? true : false;
			pArgs->m_Status.IsMenuKeyDown = HerzeleidExtractIsMenuKeyDown(Msg.LParam) ? true : false;
			pArgs->m_Status.WasKeyDown = HerzeleidExtractWasKeyDown(Msg.LParam) ? true : false;

			// Raise event
			m_HostReferencePtr->CharacterReceived.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);
		} break;
	case WM_KEYUP:
		{
			// PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Key Up" );
			KeyEventArgsPtr pArgs = new KeyEventArgs();
			pArgs->m_eVirtualKey = static_cast<VirtualKey>(Msg.WParam);
			pArgs->m_Status.RepeatCount = HerzeleidExtractRepeatCount(Msg.LParam);
			pArgs->m_Status.ScanCode = HerzeleidExtractScanCode(Msg.LParam);
			pArgs->m_Status.IsExtendedKey = HerzeleidExtractIsExtendedKey(Msg.LParam) ? true : false;
			pArgs->m_Status.IsKeyReleased = HerzeleidExtractIsKeyReleased(Msg.LParam) ? true : false;
			pArgs->m_Status.IsMenuKeyDown = HerzeleidExtractIsMenuKeyDown(Msg.LParam) ? true : false;
			pArgs->m_Status.WasKeyDown = HerzeleidExtractWasKeyDown(Msg.LParam) ? true : false;

			// Raise event
			m_HostReferencePtr->KeyUp.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;
	case WM_KEYDOWN: 
		{
			// PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Key Down" );
			KeyEventArgsPtr pArgs = new KeyEventArgs();
			pArgs->m_eVirtualKey = static_cast<VirtualKey>(Msg.WParam);
			pArgs->m_Status.RepeatCount = HerzeleidExtractRepeatCount(Msg.LParam);
			pArgs->m_Status.ScanCode = HerzeleidExtractScanCode(Msg.LParam);
			pArgs->m_Status.IsExtendedKey = HerzeleidExtractIsExtendedKey(Msg.LParam) ? true : false;
			pArgs->m_Status.IsKeyReleased = HerzeleidExtractIsKeyReleased(Msg.LParam) ? true : false;
			pArgs->m_Status.IsMenuKeyDown = HerzeleidExtractIsMenuKeyDown(Msg.LParam) ? true : false;
			pArgs->m_Status.WasKeyDown = HerzeleidExtractWasKeyDown(Msg.LParam) ? true : false;

			// Raise event
			m_HostReferencePtr->KeyDown.Invoke( 
				m_HostReferencePtr, 
				pArgs
				);

		} break;

	case WM_ACTIVATE:
		{ // Raise WindowActivated event and return.
			//if ( (Msg.LParam != (LParam)m_HostReferencePtr->m_Handle) && (!Msg.LParam) ) return; // This event is not for our window 
			WindowActivatedEventArgsPtr pArgs = new WindowActivatedEventArgs(
				(CoreWindowActivationState)(Msg.WParam)
				);

			m_HostReferencePtr->Activated.Invoke( 
				m_HostReferencePtr, // Sender window 
				pArgs // Sender args
				);

		} break;

	case WM_SIZE:
		{ // Raise WindowSizeChanged event and return. 

			{ // Finally update the bounding rect of the host window 
				RECT ClientRect;
				GetClientRect( m_HostReferencePtr->m_Handle, &ClientRect);
				m_HostReferencePtr->m_BoundingRect = ClientRect;
			}

			//if ( IsIconic(m_HostReferencePtr->m_Handle) ) // invisible to client
			//{
			//	m_bWindowWasMinimized = true; // update notifier
			//	VisibilityChangedEventArgsPtr pArgs = new VisibilityChangedEventArgs(false);
			//	m_HostReferencePtr->VisibilityChanged.Invoke( // Notify user
			//		m_HostReferencePtr, // sender window 
			//		pArgs // sender args
			//		);
			//}
			//else if (m_bWindowWasMinimized) // visible for client
			//{
			//	m_bWindowWasMinimized = false; // update notifier
			//	VisibilityChangedEventArgsPtr pArgs = new VisibilityChangedEventArgs(true);
			//	m_HostReferencePtr->VisibilityChanged.Invoke( // Notify user
			//		m_HostReferencePtr, // sender window 
			//		pArgs // sender args
			//		);
			//}
			//else
			//{
				WindowSizeChangedEventArgsPtr pArgs = new WindowSizeChangedEventArgs(
					Foundation::Size( LOWORD(Msg.LParam), HIWORD(Msg.LParam) )
					);
				m_HostReferencePtr->SizeChanged.Invoke(
					m_HostReferencePtr, // Sender window 
					pArgs // Sender args
					);
			//}
		} break;
	//case WM_SIZING: // The bounding rect should be always up-to-date 
	//	{ // Update internal buffers. 
	//		PrintToDebugWindowOnly8( "CoreWindowEventHandler > Process : Sizing" );
	//		m_BoundingRectBuffer = *((RECT*)(Msg.LParam));
	//	} break;
	}; 
}